using System;
using System.Data;
using System.Reflection;

namespace ODX.Core
{
    internal class ColumnInfo
    {
        private readonly string name;
        private readonly bool isUnique;
        private readonly int maxLength;

        private int scale;
        private string dbType;

        public ColumnInfo(string name, bool isUnique, int maxLength) : this(name, isUnique, maxLength, null, -1){}
        public ColumnInfo(string name, bool isUnique, int maxLength, string dbType, int scale)
        {
            this.name = name;
            this.isUnique = isUnique;
            this.maxLength = maxLength;
            this.dbType = dbType;
            this.scale = scale;
        }


        public string Name
        {
            get { return name; }
        }

        public bool IsUnique
        {
            get { return isUnique; }
        }

        public int MaxLength
        {
            get { return maxLength; }
        }

        public string DbType
        {
            get { return dbType; }
            set { dbType = value; }
        }


        public int Scale
        {
            get { return scale; }
            set { scale = value; }
        }
    }

    internal static class BuilderUtils
    {
        internal const BindingFlags PropertyFilter =
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;

        internal static string GetRelatedTableName(PropertyInfo pi, Polymorpher pm)
        {
            RelatedTableAttribute[] attr = (RelatedTableAttribute[])pi.GetCustomAttributes(typeof(RelatedTableAttribute), false);
            if (attr.Length > 0)
                return attr[0].Name;
            if ( pi.PropertyType.IsSubclassOf(typeof(Entity)))
                return pm.GetTypeTable(pi.PropertyType);
            return pm.GetTypeTable(pi.PropertyType.GetGenericArguments()[0]);
        }

        internal static string GetColumnName(PropertyInfo pi)
        {
            ColumnInfo ci = GetColumnInfo(pi);
            return ci != null ? ci.Name : null;
        }

        internal static ColumnInfo GetColumnInfo(PropertyInfo pi)
        {
            string name = null;
            bool isUnique = false;
            int maxLength = 50;
            int scale = -1;
            string dbType = null;

            ColumnAttribute[] attr = (ColumnAttribute[])pi.GetCustomAttributes(typeof(ColumnAttribute), true);
            if (attr.Length > 0)
            {
                isUnique = attr[0].Unique;
                name = attr[0].Name;
                maxLength = attr[0].MaxLength;
                dbType = attr[0].DbType;
                scale = attr[0].Scale;
            }

            if (name == null && pi.PropertyType.IsSubclassOf(typeof(Entity)))
            {
                name = pi.Name + "ID";
                maxLength = 22;
            }

            if (name == null && pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericArguments()[0].IsSubclassOf(typeof(Entity)))
            {
                Type childType = pi.PropertyType.GetGenericArguments()[0];
                foreach (PropertyInfo childPi in childType.GetProperties(ProxyBuilder.PropertyFilter))
                {
                    if (IsAutoProperty(childPi))
                    {
                        if (childPi.PropertyType == pi.ReflectedType)
                            return GetColumnInfo(childPi);
                        if (childPi.PropertyType.IsGenericType && childPi.PropertyType.GetGenericArguments()[0].IsSubclassOf(typeof(Entity)))
                        {
                            Type parentType = childPi.PropertyType.GetGenericArguments()[0];
                            if (parentType == pi.ReflectedType)
                                return null; // this is Many-To-Many relation. no column added to the related tables;
                        }
                    }
                }
            }

            if (name == null)
                name = pi.Name;

            return new ColumnInfo(name, isUnique, maxLength, dbType, scale);
        }

        internal static string GetRelationName(PropertyInfo pi, Session session)
        {
            string tableName = session.Pm.GetTypeTable(pi.ReflectedType);
            string colName = GetColumnName(pi);
            return FindRelation(session.Schema, tableName, colName);
        }

        internal static string FindRelation(DataSet ds, string childTable, string childFK)
        {
            foreach (DataRelation dr in ds.Relations)
                if (dr.ChildTable.TableName == childTable && dr.ChildColumns[0].ColumnName == childFK)
                    return dr.RelationName;
            return null;
        }

        internal static bool IsManualClass(Type type)
        {
            return type.GetCustomAttributes(typeof(ManualAttribute), false).Length > 0;
        }

        internal static bool IsHierarchyRoot(Type type)
        {
            return type.GetCustomAttributes(typeof(HierarchyRootAttribute), false).Length > 0;
        }

        internal static bool IsAutoProperty(PropertyInfo pi)
        {
            if ( pi.GetCustomAttributes(typeof(ManualAttribute), false).Length > 0 )
                return false;

            MethodInfo mi = pi.GetGetMethod(true) ?? pi.GetSetMethod(true);
            return mi != null ? mi.IsAbstract : false;
        }
    }
}